package cn.scs.impl;

// DataServer 代码
import cn.scs.client.MetaServerClient;
import cn.scs.common.Config;
import cn.scs.common.DataOpCode;
import cn.scs.component.FileReader;
import cn.scs.component.FileWriter;
import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

import java.io.*;
import java.net.*;

public class DataServer {
    private static final Logger log = LogManager.getLogger(DataServer.class);
    String nodeName;
    boolean isRunning = false;
    int DATA_SERVER_PORT = 9526;
    int END_STREAM = -1;
    String storage_path;
    MetaServerClient metaClient;
    ServerSocket serverSocket;
    HeartBeatThread heartBeat;

    public DataServer() {
        try {
            //获得本机名
            this.nodeName = InetAddress.getLocalHost().getHostName();
            //读取仓库路径
            storage_path = "./storage/";
            serverSocket = new ServerSocket(DATA_SERVER_PORT);
            // socket 定制初始化
            serverSocket.setReceiveBufferSize(Config.blockSize+Config.socketSize);
            // Client 操作
            metaClient = new MetaServerClient();
            heartBeat = new HeartBeatThread();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void serve() throws IOException {
        System.out.println("Data Server is running...");
        isRunning = true;
        new Thread(heartBeat).start();  // 是多线程heartBeat，原来的run是单线程
        System.out.println("heartBeat...");
        while (isRunning) {
            // 接受客户端连接
            Socket clientSocket = serverSocket.accept();
            System.out.println("waiting...");
            // 获取客户端请求的文件名和偏移量
            DataInputStream in = new DataInputStream(clientSocket.getInputStream());
            // 发送文件内容给客户端
            DataOutputStream out = new DataOutputStream(clientSocket.getOutputStream());
            DataOpCode op = DataOpCode.read(in);
            process(op,in,out);
            // 关闭客户端连接
            clientSocket.close();
        }
    }

    public class HeartBeatThread implements Runnable{
        public void run() {
            while(isRunning){
                metaClient.heartBeat(nodeName);
                try {
                    //为了清晰 改为 3秒*5=15秒 心跳一次
                    Thread.sleep((long)Config.HEARTBEAT_SECS * 1000 * 5);
                    //System.out.println("heartBeat node " + nodeName + " time:"+ new Date());
                } catch (InterruptedException ignored) {
                }
            }
        }
    }

    // 处理客户端请求并返回响应
    protected final void process(DataOpCode op, DataInputStream in, DataOutputStream out) throws IOException {
        System.out.print(op.toString()+"\t> > >\t");
        switch(op) {
            case WRITE_FILE:
                writeFile(in, out);
                break;
            case READ_FILE:
                readFile(in, out);
                break;
            case DEL_FILE:
                deleteFile(in, out);
                break;
            case CREATE_FILE:
                createFile(in, out);
                break;
            case DOWNLOAD_FILE:
                downloadFile(in, out);
                break;
            default:
                throw new IOException("Unknown op " + op + " in data stream");
        }
    }
    // new
    private void createFile(DataInputStream in, DataOutputStream out) {
        try {
            String uuid = in.readUTF();     // 获取文件UUID
            String user = in.readUTF();     // 获取用户 | 元数据已声明，此处或许应当无用
            boolean isDir = in.readBoolean();   // 目录
            // 读取block readByte封装
            byte[] dataBlock = readByte(in);
            System.out.println("CreateFile function...\tinfo: "+user+"  "+uuid);
            System.out.println("isDir:\t"+ isDir);
            if (!isDir) {
                FileWriter writer = new FileWriter(storage_path + uuid);
                writer.write(dataBlock);
                writer.closeStream();   // 关闭流
                System.out.println(storage_path + uuid + "\tCreated! In DataServer");
                out.writeInt(0);
                out.writeUTF("Created");
            } else {
                out.writeInt(-1);
                out.writeUTF("Create Failed, isDir");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    // new
    private void downloadFile(DataInputStream in, DataOutputStream out) {
        try {
            String uuid = in.readUTF();     // 获取文件UUID
            String user = in.readUTF();     // 获取用户 | 元数据已声明，此处或许应当无用
            // 读取文件进行传输
            FileReader reader = new FileReader(storage_path + uuid);
            byte[] block = reader.readOnce();
            out.writeInt(block.length); // 先声明block大小
            out.write(block);   // 写回流
            reader.closeStream();   // 关闭流
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    // new
    private void deleteFile(DataInputStream in, DataOutputStream out) {
        try {
            // delete 优化 ： 可以在DFSclient 把slave名称相同的组成UUID List 进行join聚合 一次性通信发送全部删除
            String uuid = in.readUTF();     // 获取文件UUID
            String user = in.readUTF();     // 获取用户 | 元数据已声明，此处或许应当无用
            // 删除并获取结果信息
            boolean res = new FileReader(storage_path + uuid).delete();
            System.out.print(uuid + " delete Res: " + res);
            if (res) {
                out.writeInt(0);
                out.writeUTF("Deleted");
            } else {
                out.writeInt(-1);
                out.writeUTF("Delete Failed!");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void readFile(DataInputStream in, DataOutputStream out) {
        // 创建服务器套接字，监听指定端口
        RandomAccessFile file = null;
        int retCode = -1;
        String msg = "read failed";
        try {
            String fileId = in.readUTF();
            long offset = in.readLong();
            String path = storage_path+File.separator+fileId;
            byte[] buffer = new byte[1024];
            file = new RandomAccessFile(path, "r");
            file.seek(offset); // 设置文件指针到偏移量位置

            int bytesRead = file.read(buffer);
            if(bytesRead>0){
                retCode = 0;
                out.writeInt(retCode);
                out.writeUTF("OK");
                out.flush();

                do{
                    out.writeInt(bytesRead);
                    out.write(buffer,0,bytesRead);
                    out.flush();
                    bytesRead = file.read(buffer);
                }while (bytesRead >0);
                out.writeInt(END_STREAM);
            }

        } catch (IOException e) {
            log.info(e);
            retCode = -1;
            msg = "File write error:"+e.getMessage();
            try {
                out.writeInt(retCode);
                out.writeUTF(msg);
                out.flush();
            } catch (IOException e1) {
                log.error(e1);
            }

        }finally {
            try {
                file.close();
            } catch (IOException e) {
                log.error(e);
            }
        }
    }

    private void writeFile(DataInputStream in,DataOutputStream out) {
        // 创建服务器套接字，监听指定端口
        FileOutputStream fout = null;
        int retCode = 0;
        String msg = "";
        try {
            String fileId = in.readUTF();//1.读取文件ID
            String path = storage_path+File.separator+fileId;
            fout = new FileOutputStream(path);
            byte[] buffer = new byte[1024];
            int bytesRead = in.read(buffer);//2.遍历读取
            while (bytesRead>0) {
                fout.write(buffer,0,bytesRead);
                if(bytesRead < 1024) break;
                bytesRead = in.read(buffer);
            }

            log.info("File "+ fileId+" write succuessfully!");
            msg = "File "+ fileId+" write succuessfully!";
        } catch (IOException e) {
            log.info(e);
            retCode = -1;
            msg = "File write error:"+e.getMessage();
        }finally {
            if(fout != null)
                try {
                    out.writeInt(retCode);//3.回写返回码
                    out.writeUTF(msg);//4.回写消息
                    fout.close();
                } catch (IOException e) {
                    log.info(e);
                }
        }
    }

    private static void sendMessageToMetaServer(Socket socket, String message) throws IOException {
        PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
        writer.println(message);
    }

    // 读取in 字节流
    private byte[] readByte(DataInputStream in) throws IOException {
        int size = in.readInt();
        // 获取size构建byte[]
        byte[] dataBlock = new byte[size];   // 接收区 大小为传入大小
        int pointer = 0;    // 记录偏移
        int res = 0;    // 记录读取结果
        int bufferSize = 8192;    // 每次读8kb 最为保险
        // 循环检测读取block
        while (res != -1 && pointer < size) {
            int readSize = Math.min(bufferSize, size-pointer);
            res = in.read(dataBlock, pointer, readSize);  // 偏移填充, 更新状态
            pointer = pointer + readSize;
        }
        return dataBlock;
    }

    private String getParentPath(String path) {
        int lastSeparatorIndex = path.lastIndexOf('/');
        if (lastSeparatorIndex == -1) {
            return "/";
        } else {
            if(lastSeparatorIndex == 0){
                return "/";
            }
            return path.substring(0, lastSeparatorIndex);
        }
    }

    public static void main(String[] args) {
        DataServer dataServer = new DataServer();
        try {
            dataServer.serve();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

